﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;

using System.Text;
using System.Windows.Forms;
using System.Globalization;
using AIRO.Devices.Solenoid;
using Ivi.SessionFactory.Interop;
using AIRO.Devices.Ccd;
using Ivi.Driver.Interop;
using WINX32Lib;
using System.Timers;
using System.IO;

namespace AIRO.LumSerial
{
    /// <summary>
    /// The serial state
    /// </summary>
    public enum StateType
    {
        /// <summary>
        /// Application is idle.
        /// </summary>
        Idle,
        /// <summary>
        /// Serial is running.
        /// </summary>
        Running,
        /// <summary>
        /// Serial was cancelled by user request or error occured.
        /// </summary>
        Cancelled,
        /// <summary>
        /// Serial was successfully completed.
        /// </summary>
        Completed,
        /// <summary>
        /// The program will now exit.
        /// </summary>
        Completing,
        Exit
    }


    public partial class FrmMain : Form
    {
        /// <summary>
        /// Number of field steps.
        /// </summary>
        private int bCnt;
        /// <summary>
        /// Starting field [T]
        /// </summary>
        private double bFrom;
        /// <summary>
        /// Field increment [T]
        /// </summary>
        private double bIncr;
        /// <summary>
        /// Field after completion [T]
        /// </summary>
        private double bAfter;
        /// <summary>
        /// Magnet dwell time [sec]
        /// </summary>
        private double bDwell;
        ///<summary>Current value of field</summary>
        private double bCurr;
        ///<summary>Total number of frames</summary>
        private long total;
        ///<summary>Frames completed</summary>
        private long completed;
        /// <summary>
        /// Time when serial was started
        /// </summary>
        private DateTime startTime;
        /// <summary>
        /// Magnet Power Supply
        /// </summary>
        private ISolenoid solenoid;
        /// <summary>
        /// IVI Session Factory
        /// </summary>
        IIviSessionFactory sf = new IviSessionFactoryClass();

        ///<summary>Current inner loop index</summary>
        private short x;
        private int expXDim = 0;
        private ICcd ccd;
        private bool subtractBackground;
        private bool isWinSpecType;
        private string backgroundPath;
        private Array backgroundFrame;

        private System.Timers.Timer tmr = new System.Timers.Timer();

        NumberFormatInfo numericFormatProvider = new NumberFormatInfo();

        StateType _state;
        FrmHwConfig _fmHardwareConfig;
        FrmAdvancedOptions _fmAdvancedOptions;

        private float[,] currentSpectrum;

        /// <summary>
        /// IVI logical name of Magnet Power Supply 
        /// </summary>
        private string psLogicalName;
        /// <summary>
        /// IVI logical name of CCD
        /// </summary>
        private string ccdLogicalName;

        private IDocFile wsSpectrum;

        /// <summary>The State property represents the serial state.</summary>
        /// <value>The State property gets/sets the _state data member.</value>
        public StateType State
        {
            get
            {
                return _state;
            }
            set
            {

                switch (value)
                {
                    case StateType.Idle:
                        Text = "B [idle]";
                        break;
                    case StateType.Running:
                        //gbField.Enabled = false;                        
                        btnStart.Enabled = false;
                        btnCancel.Text = "Cancel";
                        Text = "B [running]";
                        break;
                    case StateType.Cancelled:
                        lbCompletionTime.Text = "N/A";
                        if (solenoid != null)
                        {
                            if (((IIviDriver)solenoid).Initialized)
                                ((IIviDriver)solenoid).Close();
                        }
                        if ((ccd != null) && (((IIviDriver)ccd).Initialized))
                        {
                            ((IIviDriver)ccd).Close();
                        }

                        Text = "B [cancelled]";
                        gbField.Enabled = true;

                        btnStart.Enabled = true;
                        btnCancel.Text = "Exit";
                        break;
                    case StateType.Completing:
                        if (cbBAfterCompletion.Checked)
                        {
                            changeField(bAfter);
                        }
                        else
                            State = StateType.Completed;
                        break;
                    case StateType.Completed:
                        lbCompletionTime.Text = "N/A";

                        if (solenoid != null)
                        {
                            if (((IIviDriver)solenoid).Initialized)
                                ((IIviDriver)solenoid).Close();
                        }
                        if ((ccd != null) && (((IIviDriver)ccd).Initialized))
                        {
                            ((IIviDriver)ccd).Close();
                        }
                        Text = "B [completed]";
                        gbField.Enabled = true;

                        btnStart.Enabled = true;
                        btnCancel.Text = "Exit";
                        break;
                    case StateType.Exit:
                        this.Close();
                        break;
                    default:

                        break;
                }
                _state = value;

            }
        }

        #region serial logic

        /// <summary>
        /// This method is called when serial starts
        /// </summary>

        private void btnStart_Click(object sender, EventArgs e)
        {
            try
            {
                if (readParams() && frameInit())
                {
                    x = 0;
                    completed = 0;
                    State = StateType.Running;
                    changeField(bFrom);

                }
            }
            catch (Exception ex)
            {
                State = StateType.Cancelled;
                MessageBox.Show("Can't start : " + ex.Message, "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);

            }
        }
        

        private void onSerialCompleted()
        {
            ExpStore();
            State = StateType.Completing;
        }

        


        private void changeField(double b)
        {
            bCurr = b;
            
            solenoid.BeginGotoField(b);

        }


        private void onFieldSet()
        {
            
            if (State == StateType.Running)
            {
                if (completed == 0)
                {
                    startTime = DateTime.Now;
                }
                if (bDwell > 0)
                {
                    tmr.Interval = bDwell * 1000;
                    tmr.AutoReset = false;
                    
                    tmr.Start();
                }
                else
                {
                    
                    ccd.StartMeasure();
                    
                }
            }
            else if (State == StateType.Completing)
            {
                State = StateType.Completed;
            }

        }


        private void onTimer(Object sender, ElapsedEventArgs e)
        {
            
            if (State == StateType.Running)
            {
                
                ccd.StartMeasure();

            }
        }




        /// <summary>
        /// This method is called after each Winspec shot to set parameters for the next Winspec experiment.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void onExpCompleted(object sender, EventArgs e)
        {
            completed++;
            lbProgress.Text = completed.ToString();
            lbProgress.Update();
            DateTime now = DateTime.Now;
            TimeSpan experimentTime = now.Subtract(startTime);
            TimeSpan duration = new TimeSpan((long)(experimentTime.Ticks * total / completed));
            DateTime completionTime = startTime.Add(duration);
            lbCompletionTime.Text = completionTime.ToString("G");
            expXDim = ccd.GetDimension(0);
            GetAndShowFrame();
            if (State == StateType.Running)
            {

                x++;
                if (x >= bCnt)
                {
                    onSerialCompleted();
                    return;
                }
                else
                    changeField(bCurr + bIncr);

            }
        }

        void solenoid_FieldSetEvent(object sender, FieldSetEventArgs e)
        {
            tbBCurr.Text = e.Field.ToString();
            tbBCurr.Update();
            onFieldSet();
        }


        #endregion

        #region Winspec activities

        /// <summary>
        /// Called after acquision of whole output 2D image
        /// </summary>
        public void ExpStore()
        {
            object rf = 1;
            wsSpectrum.SetParam(DM_CMD.DM_LASTFRAMERDY, ref rf);
        }

        private void CreateSpectrumDoc()
        {           

            DocInfoClass di = new DocInfoClass();
            di.X = expXDim;
            di.Y = bCnt;
            di.Z = 1;
            di.dataType = dataType.X_FLOAT;
            di.bShowWindow = true;
            di.bAppend = false;
            di.FileType = docType.dt_SPE;
            di.Name = "spectrum";
            wsSpectrum = new DocFileClass();

            if (!wsSpectrum.OpenNew("", di))
            {
                if (this.State == StateType.Running) this.State = StateType.Cancelled;
                MessageBox.Show("WinSpec can't create spectrum output file");
                return;
            }

        }


        private void GetAndShowFrame()
        {
            Array frame = ccd.GetFrame(1);

            
                if (wsSpectrum == null)
                {
                    CreateSpectrumDoc();

                }
                if (currentSpectrum == null)
                {
                    currentSpectrum = new float[expXDim,1];
                }


                for (int i = 0; i < expXDim; i++)
                {
                    float background;
                    if (subtractBackground)
                    {
                        background = System.Convert.ToSingle(backgroundFrame.GetValue(i, 0));
                    }
                    else
                        background = 0;
                    currentSpectrum[i, 0] = System.Convert.ToSingle(frame.GetValue(i, 0))-background;

                }
                object frame2show = currentSpectrum;
                wsSpectrum.PutStrip(1,(short)(x+1), frame2show);
                wsSpectrum.Update();
                wsSpectrum.Save();            

        }

        #endregion


        

        void ccd_StateChangedEvent(object sender, AIRO.Devices.Ccd.StateChangedEventArgs args)
        {
            if (args.State == CcdState.Error)
            {
                if (this.State == StateType.Running) this.State = StateType.Cancelled;
                MessageBox.Show("Error : " + args.Message, "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        #region Graphical User Interface

        private void cbBAfterCompletion_CheckedChanged(object sender, EventArgs e)
        {
            tbBAfterCompletion.Enabled = cbBAfterCompletion.Checked;
            if (!tbBAfterCompletion.Enabled)
                tbBAfterCompletion.Text = "";
        }

        

        private void fmMain_Load(object sender, EventArgs e)
        {
            State = StateType.Idle;
            updateFinalField();            

        }

        private void updateFinalField()
        {
            if ((tbBIncr.Text.Length != 0) & (tbBCnt.Text.Length != 0) & (tbBFrom.Text.Length != 0))
            {
                try
                {
                    bCnt = System.Convert.ToInt32(tbBCnt.Text);
                    bFrom = System.Convert.ToDouble(tbBFrom.Text,numericFormatProvider);
                    bIncr = System.Convert.ToDouble(tbBIncr.Text,numericFormatProvider);
                }
                catch (Exception e)
                {
                    MessageBox.Show("Can't calculate final field! Some parameters are wrong!","Error!",MessageBoxButtons.OK,MessageBoxIcon.Error);
                    return;
                }
                double finalField = bFrom + (bCnt - 1) * bIncr;
                tbFinalField.Text = finalField.ToString();
            }
        }

    
        #region Check Methods
        /// <summary>
        /// minus can only be at the start of the number, and there can only be one.
        /// </summary>
        /// <returns>If it's a bad minus, we return true.</returns>
        protected bool CheckMinus(TextBox txtNumeric)
        {
            return (txtNumeric.Text.IndexOf("-") > -1) || (txtNumeric.SelectionStart > 0);
        }

        /// <summary>
        /// period can only occur once in number.
        /// </summary>
        /// <returns>If it's a bad period, we return true.</returns>
        protected bool CheckPeriod(TextBox txtNumeric)
        {
            return (txtNumeric.Text.IndexOf(".") > -1);
        }
        #endregion

        #region UI Logic


        /// <summary>
        /// Checks to see if we caught any bad characters.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void checkFloatNumTextBox(object sender, System.Windows.Forms.KeyPressEventArgs e)
        {
            bool nonNumberEntered = false;
            if (Char.IsDigit(e.KeyChar))
            {
            }
            //there are certain rules for determining if it's valid to use a period or a minus.
            //we need to check those rules.
            else if (e.KeyChar == '-')
                nonNumberEntered = CheckMinus((TextBox)sender);
            else if (e.KeyChar == '.')
                nonNumberEntered = CheckPeriod((TextBox)sender);
            else if (e.KeyChar == ',')
            {
                e.KeyChar = '.';
                nonNumberEntered = CheckPeriod((TextBox)sender);
            }
            else if (!Char.IsControl(e.KeyChar))
            {

                nonNumberEntered = true;
            }
            if (nonNumberEntered == true)
            {
                // If bad, mark it as handled so it doesn't show up.
                e.Handled = true;
            }
        }


        #endregion



        private void btnCancel_Click(object sender, EventArgs e)
        {
            switch (_state)
            {

                case StateType.Running:
                    State = StateType.Cancelled;
                    break;

                default:
                    State = StateType.Exit;
                    break;
            }
        }





        private void checkIntNumTextBox(object sender, KeyPressEventArgs e)
        {
            bool nonNumberEntered = false;



            if (Char.IsDigit(e.KeyChar))
            {
            }
            //there are certain rules for determining if it's valid to use a period or a minus.
            //we need to check those rules.
                /*
            else if (e.KeyChar == '-')
                nonNumberEntered = CheckMinus((TextBox)sender);
            else if (e.KeyChar == '.')
                nonNumberEntered = CheckPeriod((TextBox)sender);
            else if (e.KeyChar == ',')
            {
                e.KeyChar = '.';
                nonNumberEntered = CheckPeriod((TextBox)sender);
            }
                 * */

            else if (!Char.IsControl(e.KeyChar))
            {

                nonNumberEntered = true;
            }

            if (nonNumberEntered == true)
            {
                // If bad, mark it as handled so it doesn't show up.
                e.Handled = true;
            }
        

        }



        private void tbBIncr_Leave(object sender, EventArgs e)
        {
            updateFinalField();
        }

        


        private void hardwareConfigurationToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_fmHardwareConfig == null)
                _fmHardwareConfig = new FrmHwConfig(this);
            _fmHardwareConfig.ShowDialog();
        }

        private void tbBCnt_Leave(object sender, EventArgs e)
        {
            updateFinalField();
        }

        private void tbBFrom_Leave(object sender, EventArgs e)
        {
            updateFinalField();
        }

        private bool frameInit()
        {

            total = bCnt;
            this.lbProgress.Text = "0";
            this.lbTotal.Text = total.ToString();
            lbProgress.Update();
            lbTotal.Update();
            if (total <= 0)
            {
                MessageBox.Show("Can't start - invalid number of points specified", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            return true;
        }

        #endregion

        

        private bool readParams()
        {
            try
            {
                bCnt = System.Convert.ToInt32(tbBCnt.Text);
                if (this.cbBAfterCompletion.Checked)
                    bAfter = System.Convert.ToDouble(this.tbBAfterCompletion.Text, numericFormatProvider);

                if (ccdLogicalName == "")
                {
                    MessageBox.Show("Error! IVI CCD Logical Name is empty! Please check Hardware Configuration!", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return false;
                }
                ccdLogicalName = Properties.Settings.Default.CcdLogicalName;
                ccd = (ICcd)sf.CreateDriver(ccdLogicalName);
                ((Ivi.Driver.Interop.IIviDriver)ccd).Initialize(ccdLogicalName, false, true, "");
                ccd.StateChangedEvent += new EventHandler<AIRO.Devices.Ccd.StateChangedEventArgs>(ccd_StateChangedEvent);
                ccd.MeasurementCompleteEvent += new EventHandler<EventArgs>(onExpCompleted);

                bFrom = System.Convert.ToDouble(tbBFrom.Text, numericFormatProvider);
                bIncr = System.Convert.ToDouble(tbBIncr.Text, numericFormatProvider);
                bDwell = System.Convert.ToDouble(tbBDwell.Text, numericFormatProvider);
                psLogicalName = Properties.Settings.Default.PSLogicalName;
                if (psLogicalName == "")
                {
                    MessageBox.Show("Error! IVI Power Supply Logical Name is empty! Please check Hardware Configuration!", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return false;
                }
                solenoid = (ISolenoid)sf.CreateDriver(psLogicalName);
                ((Ivi.Driver.Interop.IIviDriver)solenoid).Initialize(psLogicalName, false, false, "");
                solenoid.StateChangedEvent += new EventHandler<AIRO.Devices.Solenoid.StateChangedEventArgs>(solenoid_StateChangedEvent);
                solenoid.ProgressEvent += new EventHandler<ProgressEventArgs>(solenoid_ProgressEvent);
                solenoid.FieldSetEvent += new EventHandler<FieldSetEventArgs>(solenoid_FieldSetEvent);

                subtractBackground = Properties.Settings.Default.SubtractBackground;
                backgroundPath = Properties.Settings.Default.BackgroundPath;
                isWinSpecType = Properties.Settings.Default.IsWinspecType;
                if (subtractBackground)
                {
                    if (isWinSpecType)
                    {
                        DocFileClass backgroundDoc = new DocFileClass();
                        DocInfo di = new DocInfoClass();
                        di.bShowWindow = false;
                        backgroundDoc.OpenNew(backgroundPath, di);
                        object obj=null;
                        backgroundDoc.GetFrame(1, ref obj);
                        backgroundFrame = (Array)obj;
                        backgroundDoc.Close();
                    }
                    else
                    {
                        StreamReader reader = new StreamReader(backgroundPath);
                        string bckgr = reader.ReadToEnd();
                        string endOfHeader="Wave   ;Sample"+"\r\n"+"[nm]   ;[counts]"+"\r\n";
                        string data=bckgr.Substring(bckgr.IndexOf(endOfHeader)+endOfHeader.Length);
                        string[] separator=new string[1];
                        separator[0] = "\r\n";
                        string[] pixels = data.Split(separator,StringSplitOptions.RemoveEmptyEntries);
                        backgroundFrame = new float[pixels.Length, 1];
                        for (int i = 0; i < pixels.Length; i++)
                        {
                            int pos = pixels[i].IndexOf(";");
                            string wave=pixels[i].Substring(0,pos);
                            string counts = pixels[i].Substring(pos+1);
                            backgroundFrame.SetValue(System.Convert.ToSingle(counts),i, 0);
                        }
                        
                    }
                }
                return true;
            }
            catch (Exception e)
            {
                MessageBox.Show("Can't start - some parameters are invalid\n" + e.Message, "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
        }


        

        void solenoid_ProgressEvent(object sender, ProgressEventArgs e)
        {
            tbBCurr.Text = e.Field.ToString();
            tbBCurr.Update();
        }

        void solenoid_StateChangedEvent(object sender, AIRO.Devices.Solenoid.StateChangedEventArgs e)
        {
            updateFieldState(e.State);
            if ((e.State == SolenoidState.Error) | (e.State == SolenoidState.QuenchDetected))
            {
                switch (_state)
                {
                    case StateType.Running:
                        State = StateType.Cancelled;
                        break;
                    default:
                        break;
                }
            }
        }

        private void updateFieldState(AIRO.Devices.Solenoid.SolenoidState state)
        {
            switch (state)
            {
                case AIRO.Devices.Solenoid.SolenoidState.Paused:
                case AIRO.Devices.Solenoid.SolenoidState.Idle:
                    pbFieldStatus.Image = Properties.Resources.green_circle;
                    break;
                case AIRO.Devices.Solenoid.SolenoidState.Running:
                    pbFieldStatus.Image = Properties.Resources.yellow_circle;
                    break;
                case AIRO.Devices.Solenoid.SolenoidState.QuenchDetected:
                case AIRO.Devices.Solenoid.SolenoidState.Error:
                    pbFieldStatus.Image = Properties.Resources.red_circle;

                    break;
            }
            pbFieldStatus.Update();
        }


        public FrmMain()
        {
            InitializeComponent();
            tmr.Elapsed += onTimer;           
                       

            numericFormatProvider.PercentDecimalSeparator = ".";
            numericFormatProvider.CurrencyDecimalSeparator = ".";
        }

        private void advancedOptionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_fmAdvancedOptions == null)
                _fmAdvancedOptions = new FrmAdvancedOptions(this);
            _fmAdvancedOptions.ShowDialog();
        }

        

        
    }
}
